
import java.util.regex.Pattern
import javax.script.ScriptEngineManager
import javax.script.ScriptEngine
import javax.script.Invocable



//-----------------------------------------------------------------------------------
// Block Macro
//-----------------------------------------------------------------------------------

abstract class AbstractBlockMacroFilter  {

	abstract def getPatList()

	boolean underTargetBlock

	def isMacroLine( line ){
		for( pat in getPatList() ){
			def m = pat.matcher(line)
			if( m.find() )
				return true
		}
		return false
	}
	def checkLine( line ){
		if( isMacroLine(line) ){
			if( underTargetBlock )
				underTargetBlock=false
			else
				underTargetBlock=true
		}
	}
}

class CommentoutBlockMacroFilter extends AbstractBlockMacroFilter {

	def PAT_COMMENTOUT = Pattern.compile('\\{commentout\\}');
	def PAT_LIST = [PAT_COMMENTOUT,]
	def getPatList(){ return PAT_LIST }

	def proc( text ){
		def rtext = ''
		text.eachLine{
			checkLine( it )
			if( isMacroLine( it ) )
				rtext += ''
			else if( underTargetBlock )
				rtext += ''
			else
				rtext += it

			rtext += System.getProperty('line.separator')
		}
		return rtext
	}
}

class CodeBlockMacroFilter extends AbstractBlockMacroFilter  {

	def PAT_CODE    = Pattern.compile('\\{code\\}');
	def PAT_CONSOLE = Pattern.compile('\\{console\\}');

	def PAT_LIST = [PAT_CODE,PAT_CONSOLE]
	def getPatList(){ return PAT_LIST }

	def proc( text ){
		def rtext = ''
		text.eachLine{
			checkLine( it )
			if( isMacroLine( it ) ){
				rtext += ''
			}
			else if( underTargetBlock ){
				rtext += '\t'+it
			}
			else{
				rtext += it
			}
			rtext += System.getProperty('line.separator')
		}
		return rtext
	}

}




//-----------------------------------------------------------------------------------
// Inline Macro
//-----------------------------------------------------------------------------------

abstract class AbstractInlineMacroFilter {

	abstract def getPatList()
	def isMacroLine( line ){
		for( def pat in getPatList() ){
			def m = pat.matcher(line)
			if( m.find() )
				return true
		}
		return false
	}
	def getMacroArgs( line ){
		for( def pat in getPatList() ){
			def m = pat.matcher(line)
			if( m.find() )
				return fix(m.group(1))
		}
		return []
	}
	def getMacroArg(){
		def args = getMacroArgs()
		if( args.size()>0 )
			return args[0]
		return null
	}
	def fix( args ){
		return args.split('\\|')
	}
}

class AnchorInlineMacroFilter extends AbstractInlineMacroFilter {

	def PAT = Pattern.compile('\\{anchor:(.*)?\\}');
	def getPatList(){ return [PAT,] }

	def proc(text){
		def rtext = ''
		text.eachLine{
			if( isMacroLine( it ) ){
				def args = getMacroArgs( it )
				def r='<a name="'+args[0]+'"></a>'
				rtext += r
			}
			else{
				rtext +=  it
			}
			rtext += System.getProperty('line.separator')
		}
		return rtext
	}
}

abstract class CodeOrTextIncludeInlineMacroFilter extends AbstractInlineMacroFilter {

	abstract def isCodeinclude()

	def proc(text){
		def rtext = ''
		text.eachLine{
			if( isMacroLine( it ) ){
				def path = null
				def enc  = 'UTF-8' // default encoding

				def args = getMacroArgs( it )
				if( args.size()>0)
					path = args[0]
				if( args.size()>1)
					enc = args[1]

				if( path!=null ){

					if( isCodeinclude() ){
						// make empty line code block before and after
						rtext += System.getProperty('line.separator')

						def code = new File( path ).getText(enc)
						code.eachLine{
							rtext += '\t'+it
							rtext += System.getProperty('line.separator')
						}

						rtext += System.getProperty('line.separator')
					}
					else{
						// markdown text include
						def mdtext = new File( path ).getText(enc)

						// make empty line include block before and after
						rtext += System.getProperty('line.separator')
						rtext += new Jmd().proc( mdtext )
						rtext += System.getProperty('line.separator')
					}
				}
			}
			else{
				rtext +=  it
			}
			rtext += System.getProperty('line.separator')
		}
		return rtext
	}
}

class TextIncludeInlineMacroFilter extends CodeOrTextIncludeInlineMacroFilter {
	def PAT = Pattern.compile('\\{include:(.*)?\\}');
	def getPatList(){ return [PAT,] }
	def isCodeinclude(){ return false }
}
class CodeincludeInlineMacroFilter extends CodeOrTextIncludeInlineMacroFilter {
	def PAT = Pattern.compile('\\{codeinclude:(.*)?\\}');
	def getPatList(){ return [PAT,] }
	def isCodeinclude(){ return true }
}



class PagebreakInlineMacroFilter extends AbstractInlineMacroFilter {

	def PAT = Pattern.compile('\\{pagebreak\\}');
	def getPatList(){ return [PAT,] }

	def proc(text){
		def rtext = ''
		text.eachLine{
			if( isMacroLine( it ) )
				rtext += '<div style="page-break-after:always;"></div>'
			else
				rtext +=  it

			rtext += System.getProperty('line.separator')
		}
		return rtext
	}
}

class GraphicInlineMacroFilter extends AbstractInlineMacroFilter {

	def PAT_1 = Pattern.compile('\\{graphic:(.*)?\\}');
	def PAT_2 = Pattern.compile('\\{g:(.*)?\\}');
	def PAT_3 = Pattern.compile('\\{image:(.*)?\\}');
	def PAT_4 = Pattern.compile('\\{img:(.*)?\\}');

	def getPatList(){ return [PAT_1,PAT_2,PAT_3,PAT_4,] }

	def proc(text){
		def rtext = ''
		text.eachLine{
			if( isMacroLine( it ) ){
				def args = getMacroArgs( it )
				def r='![]('+args[0]+')'
				rtext += r
			}
			else{
				rtext +=  it
			}
			rtext += System.getProperty('line.separator')
		}
		return rtext
	}
}


//-----------------------------------------------------------------------------------
// Metadata Inline Macro
// 
//     This type macro is just removed.
//-----------------------------------------------------------------------------------

class MetadataInlineMacroFilter {

	def PAT_CDATE = Pattern.compile('\\{cdate:.*?\\}');
	def PAT_MDATE = Pattern.compile('\\{mdate:.*?\\}');
	def PAT_WHAT  = Pattern.compile('\\{what:.*?\\}');
	def PAT_KEYWORD  = Pattern.compile('\\{keyword:.*?\\}');

	def PAT_LIST  = [PAT_CDATE,PAT_MDATE,PAT_WHAT,PAT_KEYWORD]


	def proc(text){
		def rtext = ''
		text.eachLine{
		
			def found = false
			for( pat in PAT_LIST ){
				def m = pat.matcher(it)
				if( m.find() ){
					def r=''
					r += it.substring(0,m.start())
					r += it.substring(m.end())
					rtext +=  r
					found = true
					break
				}
			}
			if(found==false){
				rtext +=  it
			}
			rtext += System.getProperty('line.separator')
		}
		return rtext
	}
}

class Jmd {
	def proc( text ){
		text = new MetadataInlineMacroFilter().proc(text)
		text = new CodeBlockMacroFilter().proc(text)
		text = new CommentoutBlockMacroFilter().proc(text)
		
		text = new AnchorInlineMacroFilter().proc(text)
		text = new PagebreakInlineMacroFilter().proc(text)
		text = new CodeincludeInlineMacroFilter().proc(text)
		text = new TextIncludeInlineMacroFilter().proc(text)
		text = new GraphicInlineMacroFilter().proc(text)

		return text
	}
}


class ShowdownWrapper {

        static ScriptEngineManager FACTORY = new ScriptEngineManager();
	static ScriptEngine ENGINE = FACTORY.getEngineByName("JavaScript");

	def showdownjs

	def getMyShowdownCode(){
		def code = ''
		code += 'function toHtml(text){ var html=new Showdown.converter().makeHtml(text); return html; }'
		code += System.getProperty('line.separator')

		code += getShowdownjs()

		return code
	}

	def proc(String markdownText){
	
		def sr=new StringReader(getMyShowdownCode())
		ENGINE.eval(sr);
		sr.close();

		Invocable inv = (Invocable) ENGINE;
		def html = (String)inv.invokeFunction('toHtml', markdownText );

		return html;
	}
}

class DataManager {

	def jmd_markdowntext
	def DataManager( jmd_markdowntext ){
		this.jmd_markdowntext = jmd_markdowntext
	}

	def PAT_TITLE = Pattern.compile("^#{1}([^#].*)");

	/*
	def getTitle(){
	
		def title = ''
		jmd_markdowntext.eachLine{
			def m = PAT_TITLE.matcher(it)
			if( m.find() ){
				title = m.group(1)
				break
			}
		}
	
		return title.trim()
	}
	*/
	def getTitle(){
		def title = ''
		def found = false
		jmd_markdowntext.eachLine{
			if( found == false ){
				def m = PAT_TITLE.matcher(it)
				if( m.find() ){
					title = m.group(1)
					found = true
				}
			}
		}
	
		return title.trim()
	}
	
	
	def PAT_WHAT  = Pattern.compile('\\{what:(.*)?\\}');
	def PAT_KEYWORD  = Pattern.compile('\\{keyword:(.*)?\\}');
	def PAT_LIST  = [PAT_WHAT,PAT_KEYWORD]

	def getKeywordList(){
	
		def rlist = []
		jmd_markdowntext.eachLine{
			def found = false
			for( pat in PAT_LIST ){
				def m = pat.matcher(it)
				if( m.find() ){
					def keywords = m.group(1)
					for( keyword in keywords.split(',') )
						rlist.add( keyword )
				}
			}
		}
		return rlist
	}


	private def toCalendar( datestr ){
		if( datestr==null )
			return Calendar.getInstance()

		def y=null
		def m=null
		def d=null

		def list=datestr.split('-')
		if(list.size()>0)
			y=list[0]
		if(list.size()>1)
			m=list[1]
		if(list.size()>2)
			d=list[2]

		if( y && m && d ){
			def c=Calendar.getInstance()
			c.set(Calendar.YEAR,y.toInteger())
			c.set(Calendar.MONTH,m.toInteger()-1)
			c.set(Calendar.DAY_OF_MONTH,d.toInteger())
			return c
		}

		return Calendar.getInstance()
	}

	def PAT_CDATE  = Pattern.compile('\\{cdate:(.*)?\\}');
	def getCDateCalendar(){
		def cdate = null
		jmd_markdowntext.eachLine{
			def m = PAT_CDATE.matcher(it)
			if( m.find() )
				cdate = m.group(1)
		}
		return toCalendar( cdate )
	}

	def PAT_MDATE  = Pattern.compile('\\{mdate:(.*)?\\}');
	def getMDateCalendar(){
		def mdate = null
		jmd_markdowntext.eachLine{
			def m = PAT_MDATE.matcher(it)
			if( m.find() )
				mdate = m.group(1)
		}
		return toCalendar( mdate )
	}
}



ENCODING = 'UTF-8'


def cli = new CliBuilder()
cli.i(argName:'input',    required:true  ,args:1 , 'input file')
cli.o(argName:'output',   required:true  ,args:1 , 'output file')
cli.s(argName:'showdown', required:false ,args:1 , 'showdown.js')
cli.t(argName:'template', required:false  ,args:1 , 'template file')



def options=cli.parse(args)
def inputf = new File(options.i)
def outputf = new File(options.o)


def inputtext=inputf.getText(ENCODING)
text = new Jmd().proc(inputtext)

if( outputf.name.endsWith('.markdown' ) ){
	def pw=outputf.newPrintWriter(ENCODING)
	pw.println( text )
	pw.close()
	System.exit(0)
}

if( outputf.name.endsWith('.html' ) ){

	def showdownjs = null
	if( options.s ){
		showdownjs = new File(options.s).text
	}
	else if( new File('showdown.js').exists() ){
		showdownjs = new File('showdown.js').text
	}

	if( showdownjs ){

		def sw = new ShowdownWrapper()
		sw.showdownjs = showdownjs
		def html = sw.proc( text )
	
		if( options.t ){
	
			def dataMan = new DataManager(inputtext)
			def binding=[
				outputfile : outputf,
				cdate   : dataMan.getCDateCalendar(),
				mdate   : dataMan.getMDateCalendar(),
				encoding: ENCODING,
				title   : dataMan.getTitle(),
				keywordlist : dataMan.getKeywordList(),
				html :   html,
			]
	
			def engine = new groovy.text.SimpleTemplateEngine()
			def template = engine.createTemplate( new File(options.t).getText(ENCODING) )
			html = template.make(binding).toString()
		}
	
		def pw=outputf.newPrintWriter(ENCODING)
		pw.println( html )
		pw.close()
	
		System.exit(0)
	}
	else{
		cli.usage()
		System.exit(0)
	}
}


cli.usage()


